import elasticsearch
from django.shortcuts import render
from django.db.models import Q
from rest_framework.decorators import action
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework.mixins import *
from rest_framework.permissions import IsAuthenticated  # 自带的登录权限
from rest_framework.status import HTTP_201_CREATED, HTTP_200_OK, HTTP_404_NOT_FOUND
from rest_framework.pagination import LimitOffsetPagination, PageNumberPagination
from random import sample
from elasticsearch import Elasticsearch, exceptions  # exceptions方法用于异常报错

# Create your views here.
from utils.customPermissions import MyBasePermission, auto_user
from work.models import Label, Topic
from work.serializers import *
from config.elasticsearchConfig import HOST, PORT

# 如果用列表的话,就代表着是可以放多个参数的
es = Elasticsearch([{'host': HOST, 'port': PORT}], timeout=3600)  # 得到应该Elasticsearch实例,参数是本地回环和ES的端口,timeout反应时间


class LabelViewSet(ModelViewSet):
    queryset = Label.objects.all()  # 一定要取到拿到查询集
    serializer_class = LableSerializer  # 以及它的序列化器
    permission_classes = [MyBasePermission]

    @auto_user
    def create(self, request):
        # data = request不可行,需要让实例可变
        # request.POST._mutable = True  # 让数据变成可变的
        # request.data['user']是提交过来的用户id”1“  request.user.id需要更改为当前登录用户的id
        # request.data['user'] = str(request.user.id)  # 把创建题目的id改为本人的id
        serializer = self.get_serializer(data=request.data)  # 序列化
        serializer.is_valid(raise_exception=True)  # 校验,不成功抛出错误
        serializer.save()
        return Response(serializer.data, status=HTTP_201_CREATED)

    # 因为我们需要实现的是谁创建谁才能改,所以重写update
    @auto_user
    def update(self, request, pk):  # pk:标签id
        # 当前登录用户是不是标签的创造者
        if not Label.objects.filter(pk=pk, user=request.user):
            return Response({'msg': '您没有修改的权限'})
        label = Label.objects.get(pk=pk, user=request.user)  # 拿到当前的标签号和用户
        # request.POST._mutable = True  # 如果是,就给予修改权限
        # request.data['user'] = str(request.user.id)
        serializer = self.get_serializer(label, data=request.data)  # 序列化
        serializer.is_valid(raise_exception=True)  # 校验错误,抛出异常
        serializer.save()  # 保存数据
        return Response(serializer.data, status=HTTP_200_OK)

   # 考试接口
    @action(methods=['get'], detail=True)  # detail=True判断的表示就是PK值,有pk选True,无pk选False
    def examination(self, request, pk):  # 标签pk
        # 查询标签
        try:
            label = Label.objects.get(pk=pk)
        except Label.DoesNotExist:
            return Response(status=HTTP_404_NOT_FOUND)

        serializer = LabelTopicSerializer(label)

        return Response(serializer.data)

# 实现分页(方法二)
class TopicPaginationPageNumber(PageNumberPagination):
    page_size = 5  # 默认每页5条数据
    max_page_size = 100  # 每页可展示的最大条数


class TopicViewSet(ModelViewSet):
    queryset = Topic.objects.filter(is_delete=False)
    serializer_class = TopicSerializer
    permission_classes = [MyBasePermission]
    pagination_class = TopicPaginationPageNumber

    @auto_user
    def create(self, request):
        # data = request不可行,需要让实例可变
        # request.POST._mutable = True  # 让数据变成可变的
        # request.data['user']是提交过来的用户id”1“  request.user.id需要更改为当前登录用户的id
        # request.data['user'] = str(request.user.id)  # 把创建题目的id改为本人的id
        serializer = self.get_serializer(data=request.data)  # 序列化
        serializer.is_valid(raise_exception=True)  # 校验,不成功抛出错误
        serializer.save()
        # es数据创建
        res_data = serializer.data
        es.index(index='topic', id=res_data['id'], body=res_data)

        return Response(serializer.data, status=HTTP_201_CREATED)

    # 因为我们需要实现的是谁创建谁才能改,所以重写update
    @auto_user
    def update(self, request, pk):  # pk:标签id
        # 当前登录用户是不是题目的创造者
        if not Topic.objects.filter(pk=pk, user=request.user):
            return Response({'msg': '您没有修改的权限'})
        topic = Topic.objects.get(pk=pk, user=request.user)  # 拿到当前的标签号和用户
        # request.POST._mutable = True  # 如果是,就给予修改权限
        # request.data['user'] = str(request.user.id)
        serializer = self.get_serializer(topic, data=request.data)  # 序列化
        serializer.is_valid(raise_exception=True)  # 校验错误,抛出异常
        serializer.save()  # 保存数据
        # es数据更新
        res_data = serializer.data
        es.update(index='topic', id=res_data['id'], body={'doc': res_data})
        return Response(serializer.data, status=HTTP_200_OK)

    def destroy(self, request, *args, **kwargs):
        # 数据库删除
        res_data = ModelViewSet.destroy(self, request, *args, **kwargs)
        es.delete(index='topic', id=kwargs['pk'])  # 用kwargs的原因 kwargs = {'pk':'17'}
        # es同步
        return res_data

    @action(methods=["get"], detail=False)
    def search(self, request):
        data = dict(request.query_params)
        page = int(data.get('page')[0]) if data.get('page') else 1  # 从第几条开始
        size = int(data.get('size')[0]) if data.get('size') else 5  # 每页显示条数
        subject = data.get('subject')[0] if data.get('subject') else ''  # 题目标题

        """
        GET /topic/_search
        {
          "query": {"match": {
            "subject": "python"
          }},
          "highlight": {"fields": {"subject": {}},
            "pre_tags": "<strong style='color: ligthblue'>",
            "post_tags": "</strong>"
          },
          "from": 0,
          "size": 2
        }
        """

        # 构造查询规则
        body = {
            "query": {},
            "highlight": {
                "fields": {"subject": {}},
                "pre_tags": "<strong style='color: red'>",
                "post_tags": "</strong>"
            },                          # page1:0,1,2,3,4  page2:5,6,7,8,9
            "from": (page - 1) * size,  # page=1:0 page=2:size page=3:2*size  因为web的下标索引是从0开始的
            "size": size  # 5
        }

        if not subject:
            body['query'] = {"match_all": {}}  # 没有查询目标,查询全部
        else:
            body['query'] = {"match": {"subject": subject}}  # 有搜索目标
        # 也有可能会有查询不到的情况,所以需要try一try
        try:
            es_res = es.search(index='topic', body=body)

            # 返回构造函数 es.search(index='topic', body=body)["hits"]["total"]["value"]
            count = es_res['hits']['total']['value']
            # 下一页地址
            subject_keyword = subject if subject else ''
            next = f'http://127.0.0.1:8001/work/topic/search/?page={page + 1}&size={size}&subject={subject_keyword}' if page * size < count else None
            previous = f'http://127.0.0.1:8001/work/topic/search/?page={page - 1}&size={size}&subject={subject_keyword}' if page > 1 else None

            # 代替高亮
            def highlight(i):
                if i.get('highlight'):
                    i['_source']['subject'] = i['highlight']['subject'][0]  # 替换为高亮  [0]因为highlight是列表,所以要去里面的值转为字符串
                return i['_source']  # 此时_source里面的数据就是i['highlight']['subject'][0]了

            results = [highlight(i) for i in es_res['hits']['hits']]
            res = {
                "count": count,
                "next": next,
                "previous": previous,
                "results": results
            }
        except exceptions.NotFoundError:
            res = {
                "count": 0,
                "next": None,
                "previous": None,
                "results": []
            }
        return Response(res)

    # @action(methods=["get"], detail=False)  # 应为不需要用id去查询指定的值所以为False
    # def init(self, request):
    #     """
    #     测试接口,将mysql现有数据写入到ES
    #     项目正式上线时,数据将要清空
    #     :param request:
    #     :return:
    #     """
    #     serializer = self.get_serializer(self.get_queryset(), many=True)
    #     data = serializer.data
    #     for i in data:  # 用for循环来存入数据
    #         es.index(index='topic', id=i['id'], body=i)  # body就相当于数据主体
    #     return Response(serializer.data)


# 实现分页(方法一)
class TopicPagination(LimitOffsetPagination):
    default_limit = 5  # 默认每页5条数据
    max_limit = 20  # 每页可展示的最大条数


# 优化查（让老师能查看答案,学生不可以,所以我们要重写学生的查）
class TopicStudentViewSet(GenericViewSet, ListModelMixin):
    queryset = Topic.objects.exclude(Q(label__name__contains='考试') | Q(is_delete=True))
    serializer_class = TopicNoAnswerSerializer
    permission_classes = [IsAuthenticated]  # 因为现在的接口只需要查看功能,所以登录权限就够了
    pagination_class = TopicPagination  # 只需要指定一下分页的类,就可以实现分页了

    # 随机抽题
    @action(methods=['get'], detail=False)
    def random(self, request):
        # 指定数量
        data = dict(request.query_params)  # 转为字典
        size = int(data.get('size')[0]) if data.get('size') else 3
        # int(data.get('size')[0])：因为data.get('size')是一个字典,所以需要用小标索引取值
        # data.get('size')[0]：因为下标索引取到的值是字符串,所以需要用int转换一下
        serializer = self.get_serializer(self.get_queryset(), many=True)

        # sample(),从指定序列中随机抽取指定数量的数据
        return Response(sample(serializer.data, size))

    @action(methods=["get"], detail=False)
    def search(self, request):
        data = dict(request.query_params)
        offset = int(data.get('offset')[0]) if data.get('page') else 1  # 从第几条开始
        limit = int(data.get('limit')[0]) if data.get('size') else 5  # 每页显示条数
        subject = data.get('subject')[0] if data.get('subject') else ''  # 题目标题

        # 构造查询规则
        body = {
            "query": {
                "bool": {
                        "must_not": [{"match": {"label_name": "考试"}}],  # 排除掉"label_name"是考试的题目
                        "should": []  # 或者
                }
            },
            "highlight": {
                "fields": {"subject": {}},
                "pre_tags": "<strong style='color: red'>",
                "post_tags": "</strong>"
            },               # page1:0,1,2,3,4  page2:5,6,7,8,9
            "from": offset,  # page=1:0 page=2:size page=3:2*size  因为web的下标索引是从0开始的
            "size": limit,  # 5
            "_source": {"excludes": ["answer"]}  # 去掉答案
        }

        if not subject:
            body['query']['bool']['should'].append({"match_all": {}})  # 没有查询目标,查询全部
        else:
            body['query']['bool']['should'].append({"match": {"subject": subject}})  # 有搜索目标

        # 也有可能会有查询不到的情况,所以需要try一try
        try:
            es_res = es.search(index='topic', body=body)

            # 返回构造函数 es.search(index='topic', body=body)["hits"]["total"]["value"]
            count = es_res['hits']['total']['value']
            # 下一页地址
            subject_keyword = subject if subject else ''

            next = f'http://127.0.0.1:8001/work/topicstu/?offset={offset + limit}&limit={limit}&subject={subject_keyword}' if offset * limit < count else None
            previous = f'http://127.0.0.1:8001/work/topicstu/?offset={offset - 1}&limit={limit}&subject={subject_keyword}' if offset > 1 else None

            # 代替高亮
            def highlight(i):
                if i.get('highlight'):
                    i['_source']['subject'] = i['highlight']['subject'][0]  # 替换为高亮  [0]因为highlight是列表,所以要去里面的值转为字符串
                return i['_source']  # 此时_source里面的数据就是i['highlight']['subject'][0]了

            results = [highlight(i) for i in es_res['hits']['hits']]
            res = {
                "count": count,
                "next": next,
                "previous": previous,
                "results": results
            }
        except exceptions.NotFoundError:
            res = {
                "count": 0,
                "next": None,
                "previous": None,
                "results": []
            }
        return Response(res)
